package search_algorithms;

import java.util.PriorityQueue;

import Utilities.Constants;
import Utilities.Utils;

import parsers.ArffFileIO;

import weka.core.Instances;
import classifiers.Classifier;
import data_structures.ConfigurationAccuracyPair;
import data_structures.HPConfiguration;

/**
 * Simulated-annealing based local search
 * 
 * In each iteration it looks at a random neighbor of the current configuration.
 * If the random neighbor is better, it moves to that configuration. If it is
 * not better it moves to that neighbor with probability exp(delta /
 * temperature). Currently, delta is fixed. Normally, it should decrease over
 * time.
 * 
 */
public class SimulatedAnnealer implements LocalSearcher {

    private double temperature;
    private double coolingRate;

    /**
     * @param temperature
     *            To determine the likeliness of moving among configurations.
     */
    public SimulatedAnnealer(double temperature, double coolingRate) {
	this.temperature = temperature;
	this.coolingRate = coolingRate;
    }

    @Override
	public PriorityQueue<ConfigurationAccuracyPair> optimize(Classifier classifier, 
								 Instances entireData, 
								 double portion, 
								 int confsToTry, 
								 int topK) {

	PriorityQueue<ConfigurationAccuracyPair> topKConfigurations = 
	    new PriorityQueue<ConfigurationAccuracyPair>();

	HPConfiguration curConf = classifier.getHPConfiguration();
	Instances instances = Utils.getInstancesPortion(entireData, portion);
	double curAcc = classifier.evaluateClassifier(instances, curConf);

	for (int i = 0; i < confsToTry; ++i) {
	    instances = Utils.getInstancesPortion(entireData, portion);
	    HPConfiguration nextConf = curConf.getRandomNeighbor();
	    double nextAcc = classifier.evaluateClassifier(instances, nextConf);

	    double delta = nextAcc - curAcc;

	    boolean moveToNextConf = false;
	    if (delta > 0) {
		moveToNextConf = true;
	    }

	    else {
		double probability = Math.exp(delta / temperature);
		double coinToss = Constants.RANDOM.nextDouble();

		if (coinToss < probability) {
		    moveToNextConf = true;
		}
	    }

	    if (moveToNextConf) {
		topKConfigurations.add(new ConfigurationAccuracyPair(nextConf,
								     nextAcc));
		if (topKConfigurations.size() > topK) {
		    topKConfigurations.poll();
		}
		curAcc = nextAcc;
		curConf = nextConf;
	    }
	    
	    temperature *= (1 - coolingRate);
	}

	return topKConfigurations;
    }

}
